using System;
using System.Reflection;

using NUnit.Framework;
using WCSFContrib.Extensions.EventBroker;
using WCSFContrib.Extensions.Utility;

namespace WCSFContrib.Extensions.Tests.EventBroker
{
	[TestFixture]
	public class SubscriberTests
	{
		[Test]
		public void Parameterless()
		{
			SomeMethodsToBuildDelegatesWith instance = new SomeMethodsToBuildDelegatesWith();
			Subscriber subscriber = new Subscriber(instance, new MemberReference<MethodInfo>("foo", instance.GetType().GetMethod("Parameterless")));

			Delegate result = subscriber.GetDelegate(FooEventHandlerType);
			result.DynamicInvoke(this, new DataEventArgs<string>("foo"));

			Assert.AreEqual(1, instance.ParameterlessCalls);
		}

		[Test]
		public void OneParameter()
		{
			string expectedResult = "foo";
			SomeMethodsToBuildDelegatesWith instance = new SomeMethodsToBuildDelegatesWith();
			Subscriber subscriber = new Subscriber(instance, new MemberReference<MethodInfo>(expectedResult, instance.GetType().GetMethod("OneParameter")));

			Delegate result = subscriber.GetDelegate(FooEventHandlerType);
			result.DynamicInvoke(this, new DataEventArgs<string>(expectedResult));

			Assert.AreEqual(1, instance.OneParameterCalls);
			Assert.AreEqual(expectedResult, instance.OneParameterResult);
		}

		[Test]
		public void HandlerSignature()
		{
			string topic = "foo";
			SomeMethodsToBuildDelegatesWith instance = new SomeMethodsToBuildDelegatesWith();
			Subscriber subscriber = new Subscriber(instance, new MemberReference<MethodInfo>(topic, instance.GetType().GetMethod("HandlerSignature")));

			Delegate result = subscriber.GetDelegate(FooEventHandlerType);
			DataEventArgs<string> eventArgs = new DataEventArgs<string>(topic);
			result.DynamicInvoke(this, eventArgs);

			Assert.AreEqual(1, instance.HandlerSignatureCalls);
			Assert.AreEqual(this, instance.HandlerSignatureSender);
			Assert.AreEqual(eventArgs, instance.HandlerSignatureEventArgs);
		}

		[Test]
		public void ComplexEventHandlerSignature()
		{
			string topic = "foo";
			SomeMethodsToBuildDelegatesWith instance = new SomeMethodsToBuildDelegatesWith();
			Subscriber subscriber = new Subscriber(instance, new MemberReference<MethodInfo>(topic, instance.GetType().GetMethod("SeveralParameters")));

			Delegate result = subscriber.GetDelegate(ComplexEventHandlerType);
			result.DynamicInvoke(this, new ComplexEventArgs<string, int, string>("foo", 42, "bar"));

			Assert.AreEqual(1, instance.SeveralParametersCalls);
			Assert.AreEqual("foo", instance.SeveralParametersS1);
			Assert.AreEqual(42, instance.SeveralParametersI);
			Assert.AreEqual("bar", instance.SeveralParametersS2);
		}

		/// <summary>
		/// If the user of this code does not run the sanitycheck he can get runtime exceptions when invoking the delegates cretead.
		/// </summary>
		[Test]
		[ExpectedException(typeof(TargetParameterCountException))]
		public void WrongSubscriptionSignatureThrowsOnInvoke()
		{
			Delegate result = null;

			try
			{
				SomeMethodsToBuildDelegatesWith subscribingInstance = new SomeMethodsToBuildDelegatesWith();
				Subscriber subscriber = new Subscriber(subscribingInstance, "foo", subscribingInstance.GetType().GetMethod("OneParameter"));

				// Delegate can be crated since it is created on a method signature which can create any delegate
				result = subscriber.GetDelegate(GetType().GetEvent("ComplexEvent").EventHandlerType);
			}
			catch (Exception)
			{
				Assert.Fail("All of the above should work!");
			}

			// But not invoked
			result.DynamicInvoke("foo", 42, "bar");
		}

		/// <summary>
		/// If the user of this code does not run the sanitycheck this is the kind of complicated error he may get.
		/// </summary>
		[Test]
		[ExpectedException(typeof(EventHookupException), "Unable to create delegate to method WCSFContrib.Extensions.Tests.EventBroker.SubscriberTests+SomeMethodsToBuildDelegatesWith.SpecifficHandlerSignature that points to the event System.EventHandler`1[[WCSFContrib.Extensions.Tests.EventBroker.SubscriberTests+ComplexEventArgs`3[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], WCSFContrib.Extensions.EventBroker.Tests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]!")]
		public void SpecifficSubscriberSignatureCanNotCreateOtherDelegate()
		{
			SomeMethodsToBuildDelegatesWith subscribingInstance = new SomeMethodsToBuildDelegatesWith();
			Subscriber subscriber = new Subscriber(subscribingInstance, "foo", subscribingInstance.GetType().GetMethod("SpecifficHandlerSignature"));

			// Delegate can not be crated to a method with the wrong signature
			subscriber.GetDelegate(GetType().GetEvent("ComplexEvent").EventHandlerType);
		}

		public event EventHandler<DataEventArgs<string>> FooEvent;
		public event EventHandler<ComplexEventArgs<string, int, string>> ComplexEvent;

		public class ComplexEventArgs<S, T, U> : EventArgs
		{
			private U u;
			private S s;
			private T t;

			public ComplexEventArgs(S s, T t, U u)
			{
				this.s = s;
				this.t = t;
				this.u = u;
			}

			public S theS
			{
				get { return s; }
			}

			public T theT
			{
				get { return t; }
			}

			public U theU
			{
				get { return u; }
			}
		}

		private void OnFoo(DataEventArgs<string> e)
		{
			FooEvent.Invoke(this, e);
			ComplexEvent.Invoke(this, new ComplexEventArgs<string, int, string>("foo", 42, "bar"));
		}

		private Type FooEventHandlerType { get { return GetType().GetEvent("FooEvent").EventHandlerType; } }
		private Type ComplexEventHandlerType { get { return GetType().GetEvent("ComplexEvent").EventHandlerType; } }

		private class SomeMethodsToBuildDelegatesWith
		{
			public int ParameterlessCalls = 0;

			public int OneParameterCalls = 0;
			public string OneParameterResult;

			public object HandlerSignatureSender;
			public EventArgs HandlerSignatureEventArgs;
			public int HandlerSignatureCalls = 0;

			public int SeveralParametersCalls = 0;
			public string SeveralParametersS1;
			public int SeveralParametersI;
			public string SeveralParametersS2;

			public void Parameterless()
			{
				ParameterlessCalls++;
			}

			public void OneParameter(string s)
			{
				OneParameterCalls++;
				OneParameterResult = s;
			}

			public void HandlerSignature(object sender, EventArgs e)
			{
				HandlerSignatureCalls++;
				HandlerSignatureSender = sender;
				HandlerSignatureEventArgs = e;
			}

			public void SpecifficHandlerSignature(object sender, DataEventArgs<string> e)
			{
				Assert.Fail("This delegate should not be called.");
			}

			public void SeveralParameters(string s1, int i, string s2)
			{
				SeveralParametersCalls++;
				SeveralParametersS1 = s1;
				SeveralParametersI = i;
				SeveralParametersS2 = s2;
			}
		}
	}
}